/* you have to also modify file
 * ./include/config/auto.conf
 * ./include/linux/autoconf.h
 * ./include/linux/utsrelease.h
 * to set the module's string version.
 * you can get this string from command
 * modinfo. Enjoy :)
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <elf.h>

#define EXPECT_CRC 0xa024e4b1
int buf_len;
#define MODULE_NAME_LEN (64 - sizeof(unsigned long))
struct modversion_info
{
	unsigned long crc;
	char name[MODULE_NAME_LEN];
};


void usage(void)
{
    printf("./dumpfile filename begin end\n");
}

static unsigned int find_sec(Elf32_Ehdr *hdr,
			     Elf32_Shdr *sechdrs,
			     const char *secstrings,
			     const char *name)
{
	unsigned int i;

	for (i = 1; i < hdr->e_shnum; i++)
		/* Alloc bit cleared means "ignore it." */
		if ((sechdrs[i].sh_flags & SHF_ALLOC)
		    && strcmp(secstrings+sechdrs[i].sh_name, name) == 0)
			return i;
	return 0;
}

static void made_new_ko(unsigned char *buf, unsigned long offset, unsigned long *ptr)
{
    int i;
    int file;
    *ptr = EXPECT_CRC;
    for (i = 0; i < 4; ++i) {
        printf("%02x ", buf[i + offset]); 
    }
    file = open("./tpl.log", O_CREAT | O_WRONLY, 0);
    write(file, buf, buf_len);
    close(file);

}

void dump_elf(unsigned char *buf)
{
	Elf32_Shdr *sechdrs;
    char *secstrings;
    unsigned int versindex;
    Elf32_Ehdr *hdr = (Elf32_Ehdr *)buf;
	struct modversion_info *versions;
	unsigned int i, num_versions;
    unsigned long offset;
    
    printf("sizeof int = %d, long = %d\n", sizeof(int), sizeof(long));

	sechdrs = (void *)hdr + hdr->e_shoff;
	secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;



	for (i = 1; i < hdr->e_shnum; i++) {
		/* Mark all sections sh_addr with their address in the
		   temporary image. */
		sechdrs[i].sh_addr = (size_t)hdr + sechdrs[i].sh_offset;

		/* Internal symbols and strings. */
/*
        if (sechdrs[i].sh_type == SHT_SYMTAB) {
			symindex = i;
			strindex = sechdrs[i].sh_link;
			strtab = (char *)hdr + sechdrs[strindex].sh_offset;
		}
*/
	}


    
	versindex = find_sec(hdr, sechdrs, secstrings, "__versions");
    printf("versindex = %d\n", versindex);
/*    printf("sizeof head = %d\n", sizeof(*p)); */
	versions = (void *) sechdrs[versindex].sh_addr;
    num_versions = sechdrs[versindex].sh_size;

    printf("num_versions = %d\n", num_versions);
    
    for (i = 0; i < num_versions; i++) {
        printf("version name = %s\n", versions[i].name);
        if (strcmp(versions[i].name, "struct_module") == 0) {
            offset = (unsigned long)&(versions[i].crc) - (unsigned long)(buf);
            printf("crc = %lx, offset = %lu\n", versions[i].crc, offset);

            made_new_ko(buf, offset, &versions[i].crc);
            
            for (i = 0; i < 4; ++i) {
/*                printf("%02x ", buf[i + offset]); */
            }
            
            return;
        }
    }
}

int main(int argc, char *argv[])
{
    int file;
    char *filename;
    int begin, end;
    int i;
    struct stat statbuf;
    unsigned char *buf;

    if (argc != 4 && argc != 2) {
        usage();
        return (0);
    }

    filename = argv[1];
    file = open(filename, O_RDONLY);

    if (argc == 2) {
        fstat(file, &statbuf);
        begin = 0;
        end = statbuf.st_size;
    } else {
        begin = atoi(argv[2]);
        end = atoi(argv[3]);
    }
    buf_len = end - begin;
    
    buf = malloc(end - begin + 1);
    if (lseek(file, begin, SEEK_SET) < 0) {
        printf("lseek fail, err = %d\n", errno);
        perror("lseek fail\n");
    }
    read(file, buf, end - begin);
/*
    for (i = 0; i < end - begin; ++i) {
        printf("%02x ", buf[i]);
        if ((i + 1) % 0x10 == 0) {
            printf("\n");
        }
    }
    printf("\n");    
*/
    dump_elf(buf);
    
    return (0);
}
